package com.iplay.service.user;

import java.util.Optional;

import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.iplay.component.im.IMComponent;
import com.iplay.component.im.IMException;
import com.iplay.dao.user.UserDAO;
import com.iplay.dto.ApiResponse;
import com.iplay.dto.ApiResponseMessage;
import com.iplay.entity.user.Role;
import com.iplay.entity.user.SimplifiedUserDO;
import com.iplay.entity.user.UserDO;
import com.iplay.service.exception.ResourceNotFoundException;
import com.iplay.service.user.userInfo.UserInfoService;

@Service
public class UserServiceImpl implements UserService {

	private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

	@Autowired
	private UserDAO userDAO;

	@Autowired
	private BCryptPasswordEncoder encoder;

	@Autowired
	private UserInfoService userInfoService;

	@Autowired
	private IMComponent imComponent;

	@Override
	@Transactional
	public ApiResponse<?> createOrdinaryUser(String email, String username, String password) {
		return createUserAndIMUser(username, password, email, Role.USER);
	}

	@Override
	public boolean isUsernameOccupied(String username) {
		return userDAO.findOneByUsername(username) != null;
	}

	@Override
	public boolean isEmailOccupied(String email) {
		return userDAO.findOneByEmail(email) != null;
	}

	@Override
	public ApiResponse<?> createAdministrator(String username, String password) {
		return createUserAndIMUser(username, password, username + "@FeastBooking.com", Role.ADMIN);
	}

	@Override
	public Optional<SimplifiedUserDO> findSimplifiedUserByUsername(String username) {
		return Optional.ofNullable(userDAO.findOneByUsername(username));
	}

	@Override
	public ApiResponse<?> createManager(String username, String password) {
		return createUserAndIMUser(username, password, username + "@FeastBooking.com", Role.MANAGER);
	}

	@Override
	public Optional<UserDO> findById(int id) {
		return Optional.ofNullable(userDAO.findOne(id));
	}

	@Override
	@Transactional
	public boolean deleteUser(int id) {
		userInfoService.deleteUserInfo(id);
		userDAO.delete(id);
		return true;
	}

	@Override
	public Optional<UserDO> update(UserDO user) {
		UserDO updatedUser = null;
		try {
			updatedUser = userDAO.save(user);
		} catch (DataIntegrityViolationException e) {
			LOGGER.error(e.getMessage());
		}
		return Optional.ofNullable(updatedUser);
	}

	@Override
	public boolean deleteUser(String username) {
		findSimplifiedUserByUsername(username).map(SimplifiedUserDO::getId).ifPresent(this::deleteUser);
		return true;
	}

	@Override
	public Optional<UserDO> findByUsername(String username) {
		return Optional.ofNullable(userDAO.findByUsername(username));
	}

	@Override
	public Optional<UserDO> createDeveloper(String username, String password) {
		UserDO user = null;
		try {
			user = userDAO.save(
					new UserDO(username, encoder.encode(password), username + "@FeastBooking.com", Role.DEVELOPER));
		} catch (DataIntegrityViolationException e) {
			LOGGER.error(e.getMessage());
			return Optional.empty();
		}
		return Optional.ofNullable(user);
	}

	@Transactional
	private ApiResponse<?> createUserAndIMUser(String username, String password, String email, Role role) {
		UserDO user = null;
		try {
			user = userDAO.save(new UserDO(username, encoder.encode(password), email, role));
			userInfoService.initializeUserInfo(user.getId());
		} catch (DataIntegrityViolationException e) {
			LOGGER.error(e.getMessage());
			return ApiResponse.createFailApiResponse(ApiResponseMessage.AUTH_USERNAME_OCCUPIED);
		}
		try {
			imComponent.createUser(username, encodeIMPassword(password));
		} catch (IMException e) {
			deleteUser(user.getId());
			LOGGER.error(e.getMessage(), e);
			return throwIMException();
		}
		return ApiResponse.createSuccessApiResponse(Optional.ofNullable(user));
	}

	@Override
	public ApiResponse<String> updatePassword(String email, String newPassword) {
		UserDO user = userDAO.findByEmail(email);
		if (user != null) {
			return updatePassword(user, newPassword);
		}
		return ApiResponse.createFailApiResponse(ApiResponseMessage.AUTH_EMAIL_NOT_EXISTED);
	}

	@Override
	public ApiResponse<String> updatePassword(int userId, String password, String newPassword) {
		UserDO user = findById(userId)
				.orElseThrow(() -> new ResourceNotFoundException("User with id: " + userId + " does't exist"));
		if (encoder.matches(password, user.getPassword())) {
			return updatePassword(user, newPassword);
		}
		return ApiResponse.createFailApiResponse(ApiResponseMessage.WRONG_PASSWORD);
	}

	@Transactional
	private ApiResponse<String> updatePassword(UserDO user, String newPassword) {
		String originalPassword = user.getPassword();
		user.setPassword(encoder.encode(newPassword));
		userDAO.save(user);
		try {
			imComponent.modifyUserPassword(user.getUsername(), encodeIMPassword(newPassword));
		} catch (IMException e) {
			user.setPassword(originalPassword);
			userDAO.save(user);
			LOGGER.error(e.getMessage(), e);
			return throwIMException();
		}
		return ApiResponse.SUCCESSFUL_RESPONSE_WITHOUT_MESSAGE;
	}

	private String encodeIMPassword(String password) {
		return DigestUtils.md5Hex(password);
	}

	private ApiResponse<String> throwIMException() {
		return ApiResponse.createFailApiResponse(ApiResponseMessage.IM_EXCEPTION);
	}
}
